#pragma once
#include "sock.hpp"
#include<sys/select.h>
#include<unordered_map>

#define BACK_LOG 5 //长链接个数
#define NUM sizeof(fd_set)*8
#define DFL_FD -1

namespace ns_select
{
  struct bucket
  {
    public:
    std::string inbuffer;
    std::string outbuffer;
    int in_curr;     //已经收到了多少数据
    int out_curr;  //已经发送了多少数据
    public:
    bucket():in_curr(0),out_curr(0)
    {}
  };
  class SelectServer
  {
    private:
      int listen_sock;
      unsigned short port;
      std::unordered_map<int,bucket> buckets;
    public:
      SelectServer(unsigned short _port)
        :port(_port)
      {}

      void initSelectServer()
      {
        listen_sock = ns_sock::Sock::Socket();
        ns_sock::Sock::Bind(listen_sock ,port);
        ns_sock::Sock::Listen(listen_sock,BACK_LOG);
         
      }
      
      void run()
      {
        fd_set rfds;
        int fd_array[NUM] = {0};
        clearArray(fd_array,NUM,DFL_FD); //用来初始化数据中的所有fd
        fd_array[0] = listen_sock; //把监听sock写入数组的第一个元素,

        for(;;)
        {
          //时间也是输入输出，如果间隔性的timeout返回，那么就需要对时间也进行重新设定
          //struct timeval timeout = {5,0}; //每隔5s timeout一次 
          //对所有的合法fd进行select重新设置
          int max_fd = DFL_FD;
          FD_ZERO(&rfds); //清空所有的read fd      
          //第一次循环的时候，fd_array数组中至少已经有一个fd,listen_sock
          
          for(auto i = 0;i < 1024;i ++)
          {
            if(fd_array[i] == DFL_FD)
              continue;

            //说明是需要添加的合法fd
            FD_SET(fd_array[i],&rfds);
            if(max_fd < fd_array[i])
              max_fd = fd_array[i]; //更新最大文件描述符
          }

          //1.select阻塞等待
          //2.timeval= {0},非阻塞等待轮询
          //3.阻塞+轮询，timeval={5,0}：5s之内，阻塞等待，5s之后，select返回，无论是否有事件就绪
          switch(select(max_fd+1,&rfds,nullptr,nullptr,/*&timeout*/nullptr))
          {
            case 0:
              std::cout << "timeout" << std::endl;
              break;
            case -1:
              std::cerr << "select error" << std::endl;
              break;
            default:
              //正常的事件处理
              //std:: cout << "有事件发生。。。。" << std::endl;
              HandlerEvent(rfds,fd_array,NUM);
              break;

          }
        }
      }
      
      void HandlerEvent(const fd_set& rfds,int fd_array[], int num)
      {
        //如何判断那些文件描述符就绪了？只需要判定特定的fd是否在rfds集合中
        //都有那些文件描述符?fd_array[]
        for(auto i = 0;i < num; i++)
        {
          //过滤掉不需要的fd
          if(fd_array[i] == DFL_FD)
          { 
            continue;
          }
          //是一个合法的fd，但不一定就绪了
          if(fd_array[i] ==listen_sock && FD_ISSET(fd_array[i],&rfds))
          {
            //是一个合法的fd，并且已经就绪了，是链接事件到来
            //accept
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            //在这里的时候会不会block阻塞呢？不会！
            int sock = accept(fd_array[i],(struct sockaddr*)&peer,&len);
            if(sock < 0)
            {
              std::cerr << "accept error" << std::endl;
              continue;
            }
            uint16_t peer_port = htons(peer.sin_port);
            std::string peer_ip = inet_ntoa(peer.sin_addr);
            
            struct bucket b;
            buckets.insert({sock,b});

            std::cout << "get a new link: "<<peer_ip << ":" << peer_port << std::endl;
            //是不是可以进行对应的recv?? 不可以！recv是IO，等+拷贝, select 知道！！
            //而是将该文件描述符添加到fd_array数组中！为什么？
            if(!addFdToArray(fd_array,NUM,sock))
            {
              close(sock);
              std::cout << "select server is full. close fd:" << sock << std::endl;
            }
          }
          else
          {
             //处理正常的fd
             if(FD_ISSET(fd_array[i],&rfds))
             {
               //是一个合法的fd,并且已经就绪了，是读数据事件就绪
               //实现读写，会阻塞嘛? 绝对不会
               char buffer[1024];
               
               //怎么能保证自己能拿到完整的数据？
               //1.定制协议,防止粘包问题
               //2.还要给每个sock定义对应的缓冲区,防止数据没有读完整，产生数据丢失
               ssize_t s = recv(fd_array[i], buffer,sizeof(buffer)-1,0);
               if(s > 0)
               {
                 //假设已经根据协议读完了请求
                 
                 buffer[s]=0;
                 buckets[fd_array[i]].inbuffer = buffer;
                 std::cout << "ech# " <<buffer << std::endl;
               }
               else if(s==0)
               {
                 std::cout  << "client quit" << std::endl;
                 close(fd_array[i]);
                 fd_array[i] = DFL_FD; // 清除数组中的文件描：述符
               }
               else{
                 std::cerr << "recv error" << std::endl;
               }
             }
             else{
                //TODO
             }
          }
        }
         
      }
      ~SelectServer()
      {}
      
    private:
     void  clearArray(int fd_array[],int num ,int default_fd)
     {
       for(auto i = 0; i< num;i++)
       {
          fd_array[i] = default_fd;
       }
     }
     bool addFdToArray(int fd_array[],int num,int sock)
     {
       for(int i = 0;i < num; i++)
       {
         if(fd_array[i]== DFL_FD)
         {
           fd_array[i] = sock;
           return true;
         }
       }
       //说明数组空间被使用完了
       return false;
     }

  };


}
